// exception.cc 
//	Entry point into the Nachos kernel from user programs.
//	There are two kinds of things that can cause control to
//	transfer back to here from user code:
//
//	syscall -- The user code explicitly requests to call a procedure
//	in the Nachos kernel.  Right now, the only function we support is
//	"Halt".
//
//	exceptions -- The user code does something that the CPU can't handle.
//	For instance, accessing memory that doesn't exist, arithmetic errors,
//	etc.  
//
//	Interrupts (which can also cause control to transfer from user
//	code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "syscall.h"
#include "userprog.h"
#include <stdio.h>
#include <iostream>

using namespace std;

int copyin(unsigned int vaddr, int len, char *buf) {
    // Copy len bytes from the current thread's virtual address vaddr.
    // Return the number of bytes so read, or -1 if an error occors.
    // Errors can generally mean a bad virtual address was passed in.
    // if len exceed the max length for buf, return error
    if (len > MAXFILENAME) {
        return -1;
    }
    bool result;
    int n=0;			// The number of bytes copied in
    int *paddr = new int;

    while ( n >= 0 && n < len) {
      result = machine->ReadMem( vaddr, 1, paddr );
      while(!result) // FALL 09 CHANGES
	  {
   			result = machine->ReadMem( vaddr, 1, paddr ); // FALL 09 CHANGES: TO HANDLE PAGE FAULT IN THE ReadMem SYS CALL
	  }	
      
      buf[n++] = *paddr;
     
      if ( !result ) {
	//translation failed
	return -1;
      }

      vaddr++;
    }

    delete paddr;
    return len;
}

int copyout(unsigned int vaddr, int len, char *buf) {
    // Copy len bytes to the current thread's virtual address vaddr.
    // Return the number of bytes so written, or -1 if an error
    // occors.  Errors can generally mean a bad virtual address was
    // passed in.
    bool result;
    int n=0;			// The number of bytes copied in

    while ( n >= 0 && n < len) {
      // Note that we check every byte's address
      result = machine->WriteMem( vaddr, 1, (int)(buf[n++]) );

      if ( !result ) {
	//translation failed
	return -1;
      }

      vaddr++;
    }

    return n;
}

void Create_Syscall(unsigned int vaddr, int len) {
    // Create the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  No
    // way to return errors, though...
    char *buf = new char[len+1];	// Kernel buffer to put the name in

    if (!buf) return;

    if( copyin(vaddr,len,buf) == -1 ) {
	printf("%s","Bad pointer passed to Create\n");
	delete buf;
	return;
    }

    buf[len]='\0';

    fileSystem->Create(buf,0);
    delete[] buf;
    return;
}

int Open_Syscall(unsigned int vaddr, int len) {
    // Open the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  If
    // the file is opened successfully, it is put in the address
    // space's file table and an id returned that can find the file
    // later.  If there are any errors, -1 is returned.
    char *buf = new char[len+1];	// Kernel buffer to put the name in
    OpenFile *f;			// The new open file
    int id;				// The openfile id

    if (!buf) {
	printf("%s","Can't allocate kernel buffer in Open\n");
	return -1;
    }

    if( copyin(vaddr,len,buf) == -1 ) {
	printf("%s","Bad pointer passed to Open\n");
	delete[] buf;
	return -1;
    }

    buf[len]='\0';

    f = fileSystem->Open(buf);
    delete[] buf;

    if ( f ) {
	if ((id = currentThread->space->fileTable.Put(f)) == -1 )
	    delete f;
	return id;
    }
    else
	return -1;
}

void Write_Syscall(unsigned int vaddr, int len, int id) {
    // Write the buffer to the given disk file.  If ConsoleOutput is
    // the fileID, data goes to the synchronized console instead.  If
    // a Write arrives for the synchronized Console, and no such
    // console exists, create one. For disk files, the file is looked
    // up in the current address space's open file table and used as
    // the target of the write.
    
    char *buf;		// Kernel buffer for output
    OpenFile *f;	// Open file for output

    if ( id == ConsoleInput) return;
    
    if ( !(buf = new char[len]) ) {
	printf("%s","Error allocating kernel buffer for write!\n");
	return;
    } else {
        if ( copyin(vaddr,len,buf) == -1 ) {
	    printf("%s","Bad pointer passed to to write: data not written\n");
	    delete[] buf;
	    return;
	}
    }

    if ( id == ConsoleOutput) {
        if (currentThread->spaceId > 0) {
            printf("Theater %d : ", currentThread->spaceId);
        }
      for (int ii=0; ii<len; ii++) {
	printf("%c",buf[ii]);
      }

    } else {
	if ( (f = (OpenFile *) currentThread->space->fileTable.Get(id)) ) {
	    f->Write(buf, len);
	} else {
	    printf("%s","Bad OpenFileId passed to Write\n");
	    len = -1;
	}
    }

    delete[] buf;
}

int Read_Syscall(unsigned int vaddr, int len, int id) {
    // Write the buffer to the given disk file.  If ConsoleOutput is
    // the fileID, data goes to the synchronized console instead.  If
    // a Write arrives for the synchronized Console, and no such
    // console exists, create one.    We reuse len as the number of bytes
    // read, which is an unnessecary savings of space.
    char *buf;		// Kernel buffer for input
    OpenFile *f;	// Open file for output

    if ( id == ConsoleOutput) return -1;
    
    if ( !(buf = new char[len]) ) {
	printf("%s","Error allocating kernel buffer in Read\n");
	return -1;
    }

    if ( id == ConsoleInput) {
      //Reading from the keyboard
      scanf("%s", buf);

      if ( copyout(vaddr, len, buf) == -1 ) {
	printf("%s","Bad pointer passed to Read: data not copied\n");
      }
    } else {
	if ( (f = (OpenFile *) currentThread->space->fileTable.Get(id)) ) {
	    len = f->Read(buf, len);
	    if ( len > 0 ) {
	        //Read something from the file. Put into user's address space
  	        if ( copyout(vaddr, len, buf) == -1 ) {
		    printf("%s","Bad pointer passed to Read: data not copied\n");
		}
	    }
	} else {
	    printf("%s","Bad OpenFileId passed to Read\n");
	    len = -1;
	}
    }

    delete[] buf;
    return len;
}

void Close_Syscall(int fd) {
    // Close the file associated with id fd.  No error reporting.
    OpenFile *f = (OpenFile *) currentThread->space->fileTable.Remove(fd);

    if ( f ) {
      delete f;
    } else {
      printf("%s","Tried to close an unopen file\n");
    }
}

void fork_thread(int vaddr) {
    DEBUG('a', "InitRegisters pid %d, tid %d\n", currentThread->spaceId, currentThread->threadId);
    currentThread->space->InitRegisters();
    machine->WriteRegister(PCReg, vaddr);
    machine->WriteRegister(NextPCReg, vaddr+4);
    //call Restorestate function inorder to prevent information loss while context switching.
    //write to the stack register , the starting postion of the stack for this thread.  
    // if the stack addr is (vaddr + ThreadPageNum * PageSize - 16), have to copy the code to new vaddr
    // get the stack pos for the new thread
    currentThread->space->SetPageTable();

    currentThread->space->RestoreState();
    machine->Run();
}
void Fork_Syscall(unsigned int vaddr) {
    lockForThread->Acquire();
    if(0==vaddr||(vaddr%4!=0))
        {
            printf("the function address is not valid\n");
            return;
    }
    // Fork a new thread
    Thread * thread = new Thread("thread");
    // Update the Process Table for Multiprogramming part.
    ProcessTable *pt = &processTable[currentThread->spaceId];
    thread->spaceId = currentThread->spaceId;
    thread->threadId = pt->totalThreadNum;
    ++(pt->totalThreadNum);
    ++(pt->activeThreadNum);
    // Allocate the addrespace to the thread being forked which is essentially current thread's addresspsace because threads share the process addressspace. 
    thread->space = currentThread->space;
    
    thread->Fork(fork_thread, vaddr);
    //fork_thread(vaddr, id);  // int (*fn)(void *), void * arg, unsigned long flags
    
    //++threadSum;
    lockForThread->Release();
}

void exec_thread(int vaddr) {
    // Write PCReg, NextPCReg and StackReg
    currentThread->space->InitRegisters(); 
    // Write the space ID to the register 2.
    machine->WriteRegister(RetAddrReg, currentThread->spaceId);
    currentThread->space->RestoreState();
    DEBUG('t', "exec_thread \n");
    machine->Run();
}

void Exec_Syscall(unsigned int vaddr, int len) {
    lockForThread->Acquire();
    // Read the virtual address of the name of the process from the register R4 virtualAddress = machine->ReadRegister(4).
    // Convert it to the physical address and read the contents from there , which will give you the name of the process to be executed.
    char *buf = new char[len+1];
    if (copyin(vaddr, len, buf) == -1) {
        printf("wrong file name for exec"); 
        delete buf;
        return; 
    }
    // Now Open that file using filesystem->Open.
    // Store its openfile pointer.
    OpenFile *executable = fileSystem->Open(buf);
    
    if (executable == NULL) {
        printf("Unable to open file %s\n", buf);
        return;
    }

    // Create a new thread.
    Thread * thread = new Thread("process");
    // Create new addrespace for this executable file.
    thread->spaceId = processId++;
    AddrSpace *space = new AddrSpace(executable, thread->spaceId);
    // Allocate the space created to this thread's space.
    thread->space = space;
    delete executable;
    // Fork the new thread. I call it exec_thread.
    thread->Fork(exec_thread, 0);
    lockForThread->Release();
}
int DeleteLock_Syscall(int id);
int DeleteCV_Syscall(int id);
void Exit_Syscall(int status) {
    lockForThread->Acquire();
    if (status != 0) {
        printf("thread exit: error code %d!!!", status);
    }
    ProcessTable *pt = &processTable[currentThread->spaceId];
    --(pt->activeThreadNum);
    DEBUG('a', "Exit pid %d, tid %d\n", currentThread->spaceId, currentThread->threadId);
    if (!(pt->activeThreadNum)) {
        int i;
        for (i = 0;i < MAX_PROCESS_NUM; ++i) {
            if (processTable[i].activeThreadNum) {
                break;
            }
        }
        if (i == MAX_PROCESS_NUM) {
            DEBUG('a', "last thread in last process call interrup->Halt exit\n");
            interrupt->Halt();
        }
        delete currentThread->space;
        for (i = 0;i < MAX_KERNEL_LOCK; ++i) {
            if (kernelLock[i].addrSpace == currentThread->space) {
                DeleteLock_Syscall(i);
            }
        }
        for (i = 0;i < MAX_KERNEL_CV; ++i) {
            if (kernelCV[i].addrSpace == currentThread->space) {
                DeleteCV_Syscall(i);
            }
        }
    } else {
        //currentThread->space->RemovePageTable();
    }
    lockForThread->Release();
    DEBUG('a', "end Exit pid %d, tid %d\n", currentThread->spaceId, currentThread->threadId);
    currentThread->Finish();
}

// create lock: new Lock and put it into lockTable and then set kernalLock 
int CreateLock_Syscall(unsigned int vaddr, int len) {
    lockForLock->Acquire();
    char* buf = new char[len+1];
    if (copyin(vaddr, len, buf) == -1) {
        // copyin fails
        printf("%s","Lock name read fails\n");
        delete buf;
        return -1;
    }

    Lock *lock = new Lock(buf);
    delete buf;
    int i = lockTable.Put(lock);
    if (i == -1) {
        lockForLock->Release();
        return -1;
    }
    kernelLock[i].userSum = 0;
    kernelLock[i].lock = lock;
    kernelLock[i].isToDelete = FALSE;  
    kernelLock[i].addrSpace = currentThread->space;
    lockForLock->Release();
    return i;
}

// acquire lock
int AcquireLock_Syscall(int id) {
    lockForLock->Acquire();
    void * i = lockTable.Get(id);
    if (int(i) == 0) {
        // output: no Lock;
        lockForLock->Release();
        return -1;
    }
    ++(kernelLock[id].userSum);
    lockForLock->Release();
    kernelLock[id].lock->Acquire();
    return 1;
}

int ReleaseLock_Syscall(int id) {
    lockForLock->Acquire();
    void * i = lockTable.Get(id);
    if (int(i) == 0) {
        // output: no Lock;
        lockForLock->Release();
        return -1;
    }
    --(kernelLock[id].userSum);
    kernelLock[id].lock->Release();
    lockForLock->Release();
    return 1;
}
//delete lock: do delete if userSum == 0
int DeleteLock_Syscall(int id) {
    lockForLock->Acquire();
    void * i = lockTable.Get(id);
    if (int(i) == 0) {
        // output: no Lock;
        lockForLock->Release();
        return -1;
    }
    if (kernelLock[id].userSum != 0) {
        // output: have use
        kernelLock[id].isToDelete = TRUE;
        lockForLock->Release();
        return -1;
    }
    delete kernelLock[id].lock;
    kernelLock[id].addrSpace = NULL;
    lockTable.Remove(id);
    lockForLock->Release();
    return 1;
}

int CreateCV_Syscall(unsigned int vaddr, int len) {
    lockForCondition->Acquire();
    char* buf = new char[len+1];
    if (copyin(vaddr, len, buf) == -1) {
        // copyin fails
        printf("%s","Condition Variable name read fails\n");
        delete buf;
        return -1;
    }
    Condition *condition = new Condition(buf);
    int i = conditionTable.Put(condition);
    if (i == -1) {
        lockForCondition->Release();
        return -1;
    }
    kernelCV[i].condition = condition;  
    kernelCV[i].isToDelete = FALSE;  
    kernelCV[i].userSum = 0;
    kernelCV[i].addrSpace = currentThread->space;
    lockForCondition->Release();
    return i;
}

int WaitCV_Syscall(int lockId, int CVId) {
    lockForCondition->Acquire();
    void * i = conditionTable.Get(CVId);
    if (int(i) == 0) {
        lockForCondition->Release();
        return -1;
    }
    // add this user
    ++(kernelCV[CVId].userSum);
    lockForCondition->Release();
    // ? get lock for lock and check lock
        
    Lock * lock = kernelLock[lockId].lock;
    kernelCV[CVId].condition->Wait(lock);
    // remove one user
    lockForCondition->Acquire();
    --(kernelCV[CVId].userSum);
    lockForCondition->Release();
    return 1; 
}

int SignalCV_Syscall(int lockId, int CVId) {
    lockForCondition->Acquire();
    void * i = conditionTable.Get(CVId);
    if (int(i) == 0) {
        lockForCondition->Release();
        return -1;
    }
    Lock * lock = kernelLock[lockId].lock;
    kernelCV[CVId].condition->Signal(lock);
    lockForCondition->Release();
    return 1;
}

int BroadcastCV_Syscall(int lockId, int CVId) {
    lockForCondition->Acquire();
    void * i = conditionTable.Get(CVId);
    if (int(i) == 0) {
        lockForCondition->Release();
        return -1;
    }
    Lock * lock = kernelLock[lockId].lock;
    kernelCV[CVId].condition->Broadcast(lock);
    lockForCondition->Release();
    return 1;
}

int DeleteCV_Syscall(int id) {
    lockForCondition->Acquire();
    void * i = conditionTable.Get(id);
    if (int(i) == 0) {
        lockForCondition->Release();
        return -1;
    }
    if (kernelCV[id].userSum != 0) {
        kernelCV[id].isToDelete = TRUE;
        lockForCondition->Release();
        return 0;
    }
    delete kernelCV[id].condition;
    kernelCV[id].addrSpace = NULL;
    lockForCondition->Release();
    return 1;
}

void ExceptionHandler(ExceptionType which) {
    int type = machine->ReadRegister(2); // Which syscall?
    int rv=0; 	// the return value from a syscall

    if ( which == SyscallException ) {
	switch (type) {
	    default:
		DEBUG('b', "Unknown syscall - shutting down.\n");
	    case SC_Halt:
		DEBUG('b', "Shutdown, initiated by user program.\n");
		interrupt->Halt();
		break;
	    case SC_Create:
		DEBUG('b', "Create syscall.\n");
		Create_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
		break;
	    case SC_Open:
		DEBUG('b', "Open syscall.\n");
		rv = Open_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
		break;
	    case SC_Write:
		DEBUG('b', "Write syscall.\n");
		Write_Syscall(machine->ReadRegister(4),
			      machine->ReadRegister(5),
			      machine->ReadRegister(6));
		break;
	    case SC_Read:
		DEBUG('b', "Read syscall.\n");
		rv = Read_Syscall(machine->ReadRegister(4),
			      machine->ReadRegister(5),
			      machine->ReadRegister(6));
		break;
            case SC_Close:
                DEBUG('b', "Close syscall.\n");
                Close_Syscall(machine->ReadRegister(4));
                break;
            case SC_CreateLock:
                DEBUG('b', "CreateLock syscall.\n");
                rv = CreateLock_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
                break;
            case SC_AcquireLock:
                DEBUG('b', "AcquireLock syscall. pid : %d, tid : %d, lockId : %d.\n", currentThread->spaceId, currentThread->threadId, machine->ReadRegister(4));
                rv = AcquireLock_Syscall(machine->ReadRegister(4));
                break;
            case SC_ReleaseLock:
                DEBUG('b', "ReleaseLock syscall. pid : %d, tid : %d, lockId : %d.\n", currentThread->spaceId, currentThread->threadId, machine->ReadRegister(4));
                rv = ReleaseLock_Syscall(machine->ReadRegister(4));
                break;
            case SC_DeleteLock:
                DEBUG('b', "DeleteLock syscall. pid : %d, tid : %d, lockId : %d.\n", currentThread->spaceId, currentThread->threadId, machine->ReadRegister(4));
                rv = DeleteLock_Syscall(machine->ReadRegister(4));
                break;
            case SC_CreateCV:
                DEBUG('b', "CreateCV syscall. pid : %d, tid : %d.\n", currentThread->spaceId, currentThread->threadId);
                rv = CreateCV_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
                break;
            case SC_WaitCV:
                DEBUG('b', "WaitCV syscall. pid : %d, tid : %d, lockId : %d, cvID : %d.\n", currentThread->spaceId, currentThread->threadId, machine->ReadRegister(4), machine->ReadRegister(5));
                rv = WaitCV_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
                break;
            case SC_SignalCV:
                DEBUG('b', "SignalCV syscall. pid : %d, tid : %d, lockId : %d, cvID : %d.\n", currentThread->spaceId, currentThread->threadId, machine->ReadRegister(4), machine->ReadRegister(5));
                rv = SignalCV_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
                break;
            case SC_BroadcastCV:
                DEBUG('b', "BroadcastCV syscall. pid : %d, tid : %d, lockId : %d, cvID : %d.\n", currentThread->spaceId, currentThread->threadId, machine->ReadRegister(4), machine->ReadRegister(5));
                rv = BroadcastCV_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
                break;
            case SC_DeleteCV:
                DEBUG('b', "DeleteCV syscall. pid : %d, tid : %d.\n", currentThread->spaceId, currentThread->threadId);
                rv = DeleteCV_Syscall(machine->ReadRegister(4));
                break;
	    case SC_Fork:
		DEBUG('b', "Fork syscall.\n");
		Fork_Syscall(machine->ReadRegister(4));
		break;
            case SC_Yield:
                DEBUG('b', "Yield syscall.\n");
                currentThread->Yield();
                break;
            case SC_Exec:
                DEBUG('b', "Exec syscall.\n");
                Exec_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
                break;
//            case SC_Join:
//                DEBUG('a', "Join syscall.\n");
//                Join_Syscall();
//                break;
            case SC_Exit:
                DEBUG('b', "Exit syscall.\n");
                Exit_Syscall(machine->ReadRegister(4));
                break;

	}

	// Put in the return value and increment the PC
	machine->WriteRegister(2,rv);
	machine->WriteRegister(PrevPCReg,machine->ReadRegister(PCReg));
	machine->WriteRegister(PCReg,machine->ReadRegister(NextPCReg));
	machine->WriteRegister(NextPCReg,machine->ReadRegister(PCReg)+4);
	return;
    } else {
      cout<<"Unexpected user mode exception - which:"<<which<<"  type:"<< type<<endl;
      interrupt->Halt();
    }
}
